﻿namespace SpikingAnalyticsLib.ResponseFingerPrint

open System

//=================================================================================================

/// The combined set of windows for all patterns for a given network
type WindowMapSet private(windowSet:Set<string * int * int * int * int>, combinedWindowStats:Map<string, Map<(string * int), (int * int * float)>>) =

    // For each possible stimulation pattern, store a set of windows and their associated spike count ratios
    // Note that the SAME set of windows is stored for ALL patterns
    // (although spike ratios will be low if the pattern is not the window owner)
    // Outer map is keyed by the test stimulation pattern name
    // Inner map is keyed by (windowOwner, nindex) where windowOwner is the name of the pattern that owns the window
    // This inner key is also the unique key for a window (across all patterns) and matches the key for frame.GetWindowSpikes()
    static let GetCombinedWindows (windowMaps:WindowMap list) =

        // Compute a window spike ratio for a window owned by ANOTHER pattern
        // Compute the window spike ratio (number of spikes within the window as a proportion of the total)
        let GetSpikeRatio neuronIndex minTime maxTime (responseHistograms:Map<int, Map<int, int>>) =
            let profile = responseHistograms.[neuronIndex]

            let totalSpikes =
                profile
                |> Map.fold (fun total time count -> total + count) 0

            let windowSpikes =
                profile
                |> Map.toSeq
                |> Seq.filter (fun (time, count) -> time >= minTime && time <= maxTime)
                |> Seq.sumBy (fun (time, count) -> count)

            let windowSpikeCountRatio =
                let ratio = float windowSpikes / float totalSpikes
                if Double.IsNaN(ratio) then
                    0.0
                else
                    ratio

            minTime, maxTime, windowSpikeCountRatio

        // MG ALL windows; previously this was the BEST window for each neuron
        let allWindows =
            windowMaps
            |> Seq.map (fun windowMap ->
                    let patternName = windowMap.PatternName
                    windowMap.AllWindowsIndexed
                    |> Map.toSeq
                    |> Seq.map (fun (neuronIndex, windows) ->
                            windows
                            |> Map.toSeq
                            |> Seq.map (fun (windowIndex, (offsets:int list, _, _)) ->
                                    let windowMinTime, windowMaxTime = offsets.Head, offsets |> Seq.last
                                    patternName, neuronIndex, windowIndex, windowMinTime, windowMaxTime
                                )
                        )
                    |> Seq.concat
                )
            |> Seq.concat
            |> Set.ofSeq

        let combinedWindowData =
            windowMaps
            |> Seq.map (fun windowMap ->
                    let patternName = windowMap.PatternName
                    let patternWindows = windowMap.AllWindowsIndexed
                    let frameProfile = windowMap.FrameProfile
                    let numFrames = frameProfile.NumberOfFrames
                    let patternWindowStats =
                        allWindows
                        |> Seq.map (fun (name, nindex, windowIndex, minTime, maxTime) ->
                                let windowStats =
                                    if patternName = name && patternWindows.ContainsKey nindex then
                                        let windowMinTime, windowMaxTime, windowSpikeCountRatio, _, _ = WindowMap.ComputeWindowSpikeRatios(numFrames, patternWindows.[nindex].[windowIndex])
                                        windowMinTime, windowMaxTime, windowSpikeCountRatio
                                    else
                                        // the current window is not owned by this pattern
                                        // compute additional stats
                                        GetSpikeRatio nindex minTime maxTime frameProfile.ResponseHistograms
                                (name, nindex), windowStats
                            )
                        |> Map.ofSeq
                    patternName, patternWindowStats
                )
            |> Map.ofSeq

        allWindows, combinedWindowData

//---------------------------------------------------------------------------------------------------------
// Constructors

    new (windowMaps:WindowMap list) =
        let windowSet, combinedWindowData = GetCombinedWindows windowMaps
        new WindowMapSet(windowSet, combinedWindowData)

    new (profiles:seq<FrameProfile>, includeInhibitoryNeurons) =
        let windowMaps =
            profiles
            |> Seq.map (fun profile -> new WindowMap(profile, includeInhibitoryNeurons, false))
            |> Seq.toList
        let windowSet, combinedWindowData = GetCombinedWindows windowMaps
        new WindowMapSet(windowSet, combinedWindowData)

//---------------------------------------------------------------------------------------------------------
// Properties

    /// Get the combined window statistics for all known test patterns
    member this.WindowStats = combinedWindowStats

    /// Get the combined window statistics (including spike count ratios) appropriate to the specified test pattern
    member this.GetWindowStatsForPattern(patternName) = combinedWindowStats.[patternName]

    /// Get the combined windows for all known test patterns: owner pattern, neuron, windowIndex, start, end
    member this.Windows = windowSet

    /// Get the neurons for the combined selected windows for all patterns
    member this.WindowNeurons
        with get() =
            windowSet
            |> Seq.map (fun (_, nindex, _, _, _) -> nindex)
            |> Seq.toList
